package interfaces.console;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.fenggui.ScrollContainer;
import org.fenggui.TextEditor;
import org.fenggui.binding.render.ImageFont;
import org.fenggui.decorator.background.PlainBackground;
import org.fenggui.event.key.IKeyListener;
import org.fenggui.event.key.Key;
import org.fenggui.event.key.KeyPressedEvent;
import org.fenggui.event.key.KeyReleasedEvent;
import org.fenggui.event.key.KeyTypedEvent;
import org.fenggui.util.Color;

import interfaces.GUISource;
import interfaces.console.commands.ArgumentCommand;
import interfaces.console.commands.ConsoleCommand;
import interfaces.superWidgets.FlowContent;
import interfaces.superWidgets.InterfaceLabel;
import interfaces.superWidgets.StaticContent;

public class Console extends StaticContent {
	
	private final Color BG_COLOR = Color.LIGHT_BLUE;
	
	private ImageFont labelFont;
	private HashMap<String, ConsoleCommand> commands;
	private TextEditor inputField;
	private List<ConsoleCommand> commandHistory, foundCommands;
	private ScrollContainer scrollContent;
	private FlowContent historyContent;
	private int comHistoryIndex = -1;
	private int labelHeight, labelWidth, labelX;
	
	public Console(int width, int height) {
		super(width, height, 0, height);
		
		getAppearance().add(new PlainBackground(BG_COLOR));
		
		labelFont = GUISource.smallFont;
		labelHeight = labelFont.getHeight();
		labelWidth = width;
		labelX = 0;
		
		commands = new HashMap<String, ConsoleCommand>();
		commandHistory = new ArrayList<ConsoleCommand>();
		foundCommands =  new ArrayList<ConsoleCommand>();
		
		ImageFont inputFont = GUISource.middleFont;
		
		inputField = new TextEditor();
		inputField.addKeyListener(getInputListener());
		GUISource.setTheme(inputField);
		GUISource.setFont(inputField, inputFont);
		inputField.setSize(width, inputFont.getHeight());
		inputField.getAppearance().add(new PlainBackground(BG_COLOR));
		addWidget(inputField);
		
		int scrollHeight = height - inputField.getHeight();
		scrollContent = GUISource.createScrollContainer();
		scrollContent.setShowScrollbars(true);
		scrollContent.setY(inputField.getHeight());
		scrollContent.setMinSize(width, scrollHeight);
		scrollContent.setSizeToMinSize();
		addWidget(scrollContent);
		
		historyContent = new FlowContent(width, scrollHeight, 0, 0, labelHeight);
		historyContent.setMinSize(width, scrollHeight);
		
		scrollContent.setInnerWidget(historyContent);
		
		layout();
	}
	
	private IKeyListener getInputListener() {
		return new IKeyListener() {
			@Override
			public void keyPressed(KeyPressedEvent kpe) {
				if(kpe.getKey() == '	') {
					manageTab();
					return;
				}
				
				switch(kpe.getKeyClass()) {
					case ENTER : executeCurrentCommand(); break;
					case UP : switchToHistory(true); break;
					case DOWN : switchToHistory(false); break;
				}
			}
			
			@Override
			public void keyReleased(KeyReleasedEvent keyReleasedEvent) {}
			@Override
			public void keyTyped(KeyTypedEvent keyTypedEvent) {}
		};
	}
	
	private void executeCurrentCommand() {
		if(inputField.getText().isEmpty()) return;
		
		String[] args = getInputText().split(" ");
		
		ConsoleCommand command = commands.get(args[0]);
		if(command != null) {
			addToHistory(inputField.getText(), command);
			command.execute(this, args);
			inputField.setText("");
		} else addToHistory("Not a valid Command!", null);
	}
	
	public void addToHistory(String newLine, ConsoleCommand command) {
		InterfaceLabel newLabel = new InterfaceLabel(newLine, labelFont);
		newLabel.setSize(labelWidth, labelHeight);
		newLabel.setX(labelX);
		GUISource.setTheme(newLabel);
		
		if(command != null) {
			commandHistory.add(command);
			comHistoryIndex = -1;
		}
		
		int newHeight = commandHistory.size() * labelHeight;
		if(newHeight < scrollContent.getHeight()) newHeight = scrollContent.getHeight();
		historyContent.setMinSize(historyContent.getWidth(), newHeight);
		historyContent.setSizeToMinSize();
		
		historyContent.addFlowWidget(newLabel);
		
		layout();
	}
	
	private void switchToHistory(boolean up) {
		if(up) comHistoryIndex++;
		else comHistoryIndex--;
				
		if(comHistoryIndex < 0) {
			comHistoryIndex = -1;
			inputField.setText("");
			return;
		} else if(comHistoryIndex >= commandHistory.size()) {
			comHistoryIndex = commandHistory.size();
			inputField.setText("");
			return;
		}
		
		ConsoleCommand command = commandHistory.get(comHistoryIndex);
		assert(command != null);
		
		changeInputText(command.getCommandName() + " ");
	}
	
	private void manageTab() {
		String text = getInputText();
		foundCommands.clear();
		
		for(ConsoleCommand command : commands.values()) {
			if(command.getCommandName().startsWith(text))foundCommands.add(command);
		}
		
		if(foundCommands.isEmpty()) return;
		
		if(foundCommands.size() > 1 || foundCommands.get(0) instanceof ArgumentCommand) {
			if(foundCommands.size() > 1) changeInputText(updateMinCommonString(text));
			
			addToHistory("------------------------------", null);
			
			for(ConsoleCommand command : foundCommands) {
				String postFix = "";
				if(command instanceof ArgumentCommand) 
					postFix = " = " + ((ArgumentCommand)command).getCurrentValue();
				addToHistory("    " + command.getCommandName() + postFix, null);
			}
		}
		
		if(foundCommands.size() == 1) {
			String newText = foundCommands.get(0).getCommandName();
			if(foundCommands.get(0) instanceof ArgumentCommand) newText += " ";
			changeInputText(newText);
		}
	}
	
	private String updateMinCommonString(String text) {
		int textLength = text.length();
		
		int minLen = Integer.MAX_VALUE;
		for(ConsoleCommand com1 : foundCommands) {
			String com1String = com1.getCommandName();
			
			for(ConsoleCommand com2 : foundCommands) {
				if(com1 == com2) continue;
				String com2String = com2.getCommandName();
				
				int len = 0;
				
				if(com1String.regionMatches(textLength, com2String, textLength, 1)) {
					while(com1String.regionMatches(textLength, com2String, textLength, len + 1)) {
						len++;
					}
				}
				
				if(len < minLen) {
					minLen = len;
					if(minLen == 0) break;
				}
			}
		}
		assert(minLen < Integer.MAX_VALUE && minLen >= 0);
		
		return foundCommands.get(0).getCommandName().substring(0, textLength + minLen);
	}
	
	private void changeInputText(String nexText) {
		inputField.setText("");
		
		for(int i = 0; i < nexText.length(); i++) {
			char next = nexText.charAt(i);
			Set<Key> set = new HashSet<Key>();
			set.add(Key.LETTER);
			inputField.keyTyped(new KeyTypedEvent(inputField, next, set));
		}
	}
	
	private String getInputText() {
		return inputField.getText().substring(0, inputField.getText().length() - 1);
	}
	
	public void addCommand(ConsoleCommand command) {
		commands.put(command.getCommandName(), command);
	}
	
	public void removeAllCommands() { commands.clear(); }
}